package com.immediatus.engine.modifier;


import com.immediatus.engine.contracts.IModifier;

public class LoopModifier<T> extends BaseModifier<T> implements IModifier.IModifierListener<T>{
    public static final int PERMANENT_LOOP = -1;

    private float _secondsElapsed;
    private final float _duration;

    private final IModifier<T> _modifier;

    private ILoopModifierListener<T> _loopModifierListener;

    private final int _loopCount;
    private int _loop;

    private boolean _modifierStartedCalled;
    private boolean _finishedCached;

    public LoopModifier(final IModifier<T> modifier_){
        this(modifier_, PERMANENT_LOOP);
    }

    public LoopModifier(final IModifier<T> modifier_, final int loopCount_){
        this(modifier_, loopCount_, null, null);
    }

    public LoopModifier(final IModifier<T> modifier_, final int loopCount_, final IModifierListener<T> loopListener_){
        this(modifier_, loopCount_, null, loopListener_);
    }

    public LoopModifier(final IModifier<T> modifier_, final int loopCount_, final ILoopModifierListener<T> loopListener_){
        this(modifier_, loopCount_, loopListener_, null);
    }

    public LoopModifier(final IModifier<T> modifier_, final int loopCount_, final ILoopModifierListener<T> loopListener_, final IModifierListener<T> listener_){
        super(listener_);
        this._modifier = modifier_;
        this._loopCount = loopCount_;
        this._loopModifierListener = loopListener_;
        this._loop = 0;
        this._duration = loopCount_ == PERMANENT_LOOP ? Float.POSITIVE_INFINITY : modifier_.getDuration() * loopCount_;
        this._modifier.addModifierListener(this);
    }

    protected LoopModifier(final LoopModifier<T> modifier_){
        this(modifier_._modifier.deepCopy(), modifier_._loopCount);
    }

    @Override
    public LoopModifier<T> deepCopy(){
        return new LoopModifier<T>(this);
    }

    public ILoopModifierListener<T> getLoopModifierListener(){
        return this._loopModifierListener;
    }

    public void setLoopModifierListener(final ILoopModifierListener<T> loopListener_){
        this._loopModifierListener = loopListener_;
    }

    @Override
    public float getSecondsElapsed(){
        return this._secondsElapsed;
    }

    @Override
    public float getDuration(){
        return this._duration;
    }

    @Override
    public float onUpdate(final float secondsElapsed_, final T item_){
        if (this.isFinished()){
            return 0;
        } else{
            float secondsElapsedRemaining = secondsElapsed_;

            this._finishedCached = false;
            while (secondsElapsedRemaining > 0 && !this._finishedCached){
                secondsElapsedRemaining -= this._modifier.onUpdate(secondsElapsedRemaining, item_);
            }
            this._finishedCached = false;

            final float secondsElapsedUsed = secondsElapsed_ - secondsElapsedRemaining;
            this._secondsElapsed += secondsElapsedUsed;
            return secondsElapsedUsed;
        }
    }

    @Override
    public void reset(){
        this._loop = 0;
        this._secondsElapsed = 0;
        this._modifierStartedCalled = false;

        this._modifier.reset();
    }

    @Override
    public void onModifierStarted(final IModifier<T> modifier_, final T item_){
        if (!this._modifierStartedCalled){
            this._modifierStartedCalled = true;
            this.onModifierStarted(item_);
        }
        if (this._loopModifierListener != null){
            this._loopModifierListener.onLoopStarted(this, this._loop, this._loopCount);
        }
    }

    @Override
    public void onModifierFinished(final IModifier<T> modifier_, final T item_){
        if (this._loopModifierListener != null){
            this._loopModifierListener.onLoopFinished(this, this._loop, this._loopCount);
        }

        if (this._loopCount == PERMANENT_LOOP){
            this._secondsElapsed = 0;
            this._modifier.reset();
        } else{
            this._loop++;
            if (this._loop >= this._loopCount){
                this.setFinished(true);
                this._finishedCached = true;
                this.onModifierFinished(item_);
            } else{
                this._secondsElapsed = 0;
                this._modifier.reset();
            }
        }
    }

    public interface ILoopModifierListener<T>{
        public void onLoopStarted(final LoopModifier<T> loopModifier_, final int loop_, final int loopCount_);
        public void onLoopFinished(final LoopModifier<T> loopModifier_, final int loop_, final int loopCount_);
    }
}
